﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace Edc.MantisClient
{
    public class Mantis : IDisposable
    {
        #region ctor

        private WebBrowser _Browser;

        public Mantis(string serverUrl)
        {
            this.ServerUrl = serverUrl;

            _Browser = GetBrowser();

            #region Tentativo con altro thread
            //WebBrowser browser = null;
            //var thread = new Thread(() =>
            //{
            //    var form = new BrowserForm();
            //    browser = form.browser;
            //    form.Visible = false;
            //    _waitLoad.Set();
            //    Application.Run(form);
            //});
            //thread.ApartmentState = ApartmentState.STA;
            //thread.Start();
            //_waitLoad.WaitOne();
            #endregion
        }

        #endregion

        #region Public Members

        public string ServerUrl { get; internal set; }

        public bool ExistsIssue(int issueId)
        {
            return GetIssue(issueId, true) != null;
        }

        public Issue GetIssue(int id)
        {
            return GetIssue(id, false);
        }

        public Issue GetIssue(int id, bool optional)
        {
            HtmlDocument document = null;
            try
            {
                document = LoadIssue(id, _Browser);
            }
            catch (MantisException ex)
            {
                if (ex.ErrorCode == ErrorCode.IssueNotFound && optional) return null;
                throw;
            }

            var issue = new Issue(id);

            //<TD colSpan=6></TD></TR><!-- Summary -->
            //<TR class=row-1>
            //<TD class=category>Summary </TD>
            //<TD colSpan=5>0007879: Replace IAttachmentEntityChild with explicit interface implementation of IAttachment </TD></TR><!-- Description -->
            string text = null;
            text = document.Body.InnerHtml.Split(new string[]{"<!-- Summary -->", "<!-- Description -->"}, StringSplitOptions.None)[1];
            text = text.Split(new string[] { "<TD colSpan=5>", "</TD></TR>" }, StringSplitOptions.None)[1].Trim();
            issue.Summary = text.Substring(9).Trim();

            text = document.Body.InnerHtml.Split(new string[] { "<!-- Description -->", "<!-- Steps to Reproduce -->" }, StringSplitOptions.None)[1];
            text = text.Split(new string[] { "<TD colSpan=5>", "</TD></TR>" }, StringSplitOptions.None)[1].Trim();
            issue.Description = text.Replace("<BR>", "\n").Trim();

            //    Assembly Name1		</td> 
            //<td colspan="5"> 
            //A02_domain_entitybase_2241.sql|080201101843_domain_property_2241.sql		</td> 
            var assembliesText = document.Body.InnerHtml.Split(new string[] { "<!-- Custom Fields -->" }, StringSplitOptions.None)[1];
            assembliesText = assembliesText.Split(new string[] { "<!-- spacer -->" }, StringSplitOptions.None)[0];
            issue.Assembly1 = GetAssembly(assembliesText, 1);
            issue.Assembly2 = GetAssembly(assembliesText, 2);
            issue.Assembly3 = GetAssembly(assembliesText, 3);
            issue.Assembly4 = GetAssembly(assembliesText, 4);
            issue.Assembly5 = GetAssembly(assembliesText, 5);
            try
            {
                issue.Assembly6 = GetAssembly(assembliesText, 6);
            }
            catch (Exception)
            {
                // Alcuni clienti non hanno assembly6!
            }

            return issue;
        }

        public void UpdateIssue(Issue issue)
        {
            UpdateIssueHtml(issue, _Browser);
            _Browser.Document.Forms[2].InvokeMember("submit");
            WaitLoad(_Browser);
        }

        public void EditIssue(Issue issue, IssueUpdateMode mode)
        {
            // Usiamo un browser nuovo, perché verrà poi nascosto dall'utente, e questo interferisce col successivo funzionamento.
            var browser = GetBrowser();

            switch (mode)
            {
                case IssueUpdateMode.Update:
                    UpdateIssueHtml(issue, browser);
                    break;
                case IssueUpdateMode.View:
                    LoadIssue(issue.Id, browser);
                    break;
            }
            browser.FindForm().ShowDialog();
        }

        #endregion

        #region Events

        void _browser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
        {
            if (e.Url.AbsolutePath.EndsWith("bug_update.php") && ((WebBrowser) sender).Url.PathAndQuery.Contains("bug_update_advanced_page.php?bug_id="))
            {
                int issueId = Int32.Parse(((WebBrowser)sender).Url.Query.Substring("?bug_id=".Length));
                OnIssueUpdated(issueId);
            }
        }

        public event EventHandler<IssueEventArgs> IssueUpdated;

        private void OnIssueUpdated(int issueId)
        {
            if (IssueUpdated != null) IssueUpdated(this, new IssueEventArgs() { IssueId = issueId });
        }

        public event EventHandler Working;
        public event EventHandler Done;

        #endregion

        #region Private Members

        private void UpdateIssueHtml(Issue issue, WebBrowser browser)
        {
            issue.ProcessPendingItems();

            // Files
            var document = LoadIssue(issue.Id, browser);
            foreach (var file in issue.PendingFiles)
            {
                var fileForm = document.Forms[8];
                fileForm.GetElementsByTagName("input")["file"].SetAttribute("value", file);
                fileForm.InvokeMember("submit");
            }
            issue.PendingFiles.Clear();

            // Campi normali
            var updateDocument = GetDocument("bug_update_advanced_page.php?bug_id=" + issue.Id, browser);
            var form = updateDocument.Forms[2];
            form.GetElementsByTagName("input")["summary"].SetAttribute("value", issue.Summary);
            form.GetElementsByTagName("textarea")["description"].InnerText = issue.Description;

            // Campi custom (assembly)
            form.GetElementsByTagName("input")["custom_field_1"].SetAttribute("value", issue.Assembly1.Name);
            form.GetElementsByTagName("input")["custom_field_2"].SetAttribute("value", issue.Assembly1.Version);
            form.GetElementsByTagName("input")["custom_field_3"].SetAttribute("value", issue.Assembly2.Name);
            form.GetElementsByTagName("input")["custom_field_4"].SetAttribute("value", issue.Assembly2.Version);
            form.GetElementsByTagName("input")["custom_field_7"].SetAttribute("value", issue.Assembly3.Name);
            form.GetElementsByTagName("input")["custom_field_10"].SetAttribute("value", issue.Assembly3.Version);
            form.GetElementsByTagName("input")["custom_field_8"].SetAttribute("value", issue.Assembly4.Name);
            form.GetElementsByTagName("input")["custom_field_11"].SetAttribute("value", issue.Assembly4.Version);
            form.GetElementsByTagName("input")["custom_field_9"].SetAttribute("value", issue.Assembly5.Name);
            form.GetElementsByTagName("input")["custom_field_12"].SetAttribute("value", issue.Assembly5.Version);
            try
            {
                form.GetElementsByTagName("input")["custom_field_14"].SetAttribute("value", issue.Assembly6.Name);
                form.GetElementsByTagName("input")["custom_field_13"].SetAttribute("value", issue.Assembly6.Version);
            }
            catch (Exception)
            {
                // Alcuni clienti non hanno assembly6!
            }

            // Nota
            // Possiamo fare solo 1 nota
            form.GetElementsByTagName("textarea")["bugnote_text"].InnerText = issue.PendingNote.Text;
            form.GetElementsByTagName("input")["time_tracking"].SetAttribute("value", issue.PendingNote.TimeTracking.ToString());
            form.GetElementsByTagName("input")["private"].SetAttribute("checked", issue.PendingNote.Private ? "checked" : null);
            issue.PendingNote = null;
        }

        private WebBrowser GetBrowser()
        {
            var fb = new BrowserForm();
            var browser = fb.browser;
            browser.Navigating += new WebBrowserNavigatingEventHandler(_browser_Navigating);
            var waitLoad = new AutoResetEvent(false);
            browser.DocumentCompleted += (object sender, WebBrowserDocumentCompletedEventArgs e) => { waitLoad.Set(); };
            browser.Tag = waitLoad;

            fb.Show();
            fb.Visible = false;

            return browser;
        }

        private HtmlDocument GetDocument(string url, WebBrowser browser)
        {
            if (Working != null) Working(this, EventArgs.Empty);
            browser.Navigate(new Uri(ServerUrl + url));
            WaitLoad(browser);
            if (Done != null) Done(this, EventArgs.Empty);

            var document = browser.Document;

            CheckAuthentication(browser);
            CheckError(document);

            return document;
        }

        private void WaitLoad(WebBrowser browser)
        {
            var waitLoad = (AutoResetEvent)browser.Tag;
            while (!waitLoad.WaitOne(100))
            {
                Application.DoEvents();
            }
        }

        private HtmlDocument LoadIssue(int id, WebBrowser browser)
        {
            return GetDocument("view.php?id=" + id, browser);
        }
        
        private void CheckError(HtmlDocument document)
        {
            //<TD class=form-title>APPLICATION ERROR #1100</TD></TR>
            //<TR>
            //<TD>
            //<P class=center style="COLOR: red">Issue 565656 not found.</P></TD></TR>
            //<TR>
            //<TD>
            var re = new Regex(@"<td class=form-title>APPLICATION ERROR \#(?<code>.*)</td></tr>\s*<tr>\s*<td>\s*<p.*>(?<message>.*)</p>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            var match = re.Match(document.Body.InnerHtml);
            if (!match.Success) return;

            var code = Int32.Parse(match.Groups["code"].Value.Trim());
            var message = match.Groups["message"].Value.Trim();
            throw new MantisException((ErrorCode)code, message);
        }

        private void CheckAuthentication(WebBrowser browser)
        {
            // Ci viene proposta la maschera di login?
            if (browser.Document.Forms.Count > 0 && browser.Document.Forms[0].GetAttribute("action") == "login.php")
            {
                var form = browser.Document.Forms[0];
                form.GetElementsByTagName("input")["username"].SetAttribute("value", "capursi");
                form.GetElementsByTagName("input")["password"].SetAttribute("value", "mermecRules");
                form.InvokeMember("submit");
                WaitLoad(browser);
            }

            // Login riuscita?
            if (browser.Document.Body.InnerHtml == "Your account may be disabled or blocked or the username/password you entered is incorrect.")
            {
                throw new MantisException(ErrorCode.AuthenticationFailed, "Your account may be disabled or blocked or the username/password you entered is incorrect.");
            }
        }

        private Assembly GetAssembly(string text, int assembly)
        {
            var re = new Regex(@"<td class=category>Assembly Name" + assembly + @"\s*</td>\s*\<td colspan=5>(?<assembly>.*)</td>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            var match = re.Match(text);
            if (!match.Success) throw new Exception("Assembly Name not recognized");
            var name = match.Groups["assembly"].Value.Trim();

            re = new Regex(@"<td class=category>Assembly Version" + assembly + @"\s*</td>\s*\<td colspan=5>(?<assembly>.*)</td>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            match = re.Match(text);
            if (!match.Success) throw new Exception("Assembly Version not recognized");
            var version = match.Groups["assembly"].Value.Trim();

            return new Assembly() { Name = name, Version = version };
        }

        #endregion


        #region IDisposable Members

        public void Dispose()
        {
            if (_Browser != null)
            {
                _Browser.Dispose();
                _Browser = null;
            }
        }

        #endregion
    }
}
